#include <prism/frontend/syntactical_analysis/parse.hpp>
//=====================================================================
#include <atma/assert.hpp>
//=====================================================================
#include <sooty/frontend/syntactic_analysis/parser.hpp>
//=====================================================================
#include <prism/frontend/lexical_analysis/lex.hpp>
//=====================================================================
using namespace sooty;
using namespace prism;
//=====================================================================

void bad_dent( parse_results_t& R ) {
	if (!R.full) std::cout << "bad indentation at " << R.start->position << std::endl;
}


//=====================================================================
//=====================================================================
//sooty::parseme_ptr int_type_parseme = sooty::parseme::create( 

//=====================================================================
// pre-declared parsers
//=====================================================================
namespace 
{
parser
	indenter,
	dedenter,
	ending_semi,
	
	start,
	
	function,
	function_parameter_list,
	parameter,
	function_body,
	block,
	
	dereference,
	address_of,
	
	identifier,
	typeof,
	type,
	
	pointer,
	pointer_helper,
	new_,
	delete_statement,
	
	function_call,
	function_call_argument_list,
	
	arrow,
	variable_definition,


	statement,
	expression_statement,
	return_statement,
	variable_definition_statement,
	if_statement,
	
	
	expression,
	assignment_expression,
	equality_expression,
	additive_expression,
	multiplicative_expression,
	unary_expression
	;
}
sooty::parseme_ptr N;


SOOTY_FUNCTOR(print_next_lexid, 1, 0)
{
	std::cout << arg1 << (R.stop)->id << std::endl;
}


/*
parse_results_t new_thing(const sooty::lexeme_list::const_iterator& begin, const sooty::lexeme_list::const_iterator& end, sooty::parseme_ptr& P)
{
	return parse_results_t(true, true, false, begin, end, P);
}
*/

void initialise_parsers()
{
	indenter =
		match(lexid::indent, false); //.on_failure(bad_dent);

	dedenter =
		match(lexid::dedent, false); //.on_failure(bad_dent);
	
	ending_semi =
		match(lexid::semi, false);
	
	identifier =
		match_insert(lexid::identifier, parsid::identifier);
	
	typeof =
		match(lexid::typeof_keyword),
		match(lexid::lparen),
		insert(parsid::typeof) [
			expression
		],
		match(lexid::rparen)
		;
	
	block = 
		insert(ID::block) [ guard(indenter >> *statement >> dedenter) ];
	
	type =
	      pointer
		| match_insert(lexid::int_keyword, parsid::int_type)
		| match_insert(lexid::real_keyword, parsid::real_type)
		| match_insert(lexid::bool_keyword, parsid::bool_type)
		| typeof
		;
	
	N = sooty::parseme::create( parseme_ptr(), 0, value_t(), lexical_position() );
	pointer =
		sooty::assign_to(N)
		[
		    match_insert(lexid::int_keyword, parsid::int_type)
		  | match_insert(lexid::real_keyword, parsid::real_type)
		  | match_insert(lexid::bool_keyword, parsid::bool_type)
		]
		>>
		pointer_helper
		;
		
	pointer_helper =
		match_insert(lexid::star, ID::pointer_type)
		[
			  pointer_helper
			| sooty::insert_from(N)
		]
		;
	
	function_parameter_list =
		match_insert(lexid::lparen, parsid::parameter_list)
			[
				!variable_definition >> *(match(lexid::comma, false) >> variable_definition)
			]
		>> match(lexid::rparen, false)
		;
	
	dereference = 
		insert(ID::dereference)
		[
			match(lexid::star, false) >> (  ( match(lexid::lparen, false) >> expression >> match(lexid::rparen, false) ) | identifier )
		];
	
	address_of = 
		insert(ID::address_of)
		[
			match(lexid::ampersand, false) >> (  ( match(lexid::lparen, false) >> expression >> match(lexid::rparen, false) ) | identifier )
		];
	
	function_call_argument_list =
		match_insert(lexid::lparen, ID::function_call_argument_list) [
			!expression >> *(match(lexid::comma, false) >> expression)
		]
		>> match(lexid::rparen, false)
		;
	
	function_call = 
		insert(ID::function_call) [
			identifier,
			function_call_argument_list
		];
	
	arrow = match(lexid::dash, false) >> match(lexid::rtri, false);


	expression = 
		//assignment_expression;
	
	//assignment_expression =
		  insert(ID::assignment) [ unary_expression >> match(lexid::equals, false) >> expression ]
		| equality_expression
		;
	
	equality_expression =
		  insert(ID::equ) [ additive_expression >> match(lexid::equality, false) >> additive_expression ]
		| additive_expression
		;
	
	additive_expression =
		  insert(ID::add) [ multiplicative_expression >> +(match(lexid::plus, false) >> additive_expression) ]
		| insert(ID::sub) [ multiplicative_expression >> +(match(lexid::dash, false) >> additive_expression) ]
		| multiplicative_expression
		;
	
	multiplicative_expression =
		  insert(ID::mul) [ unary_expression >> +(match(lexid::star, false) >> multiplicative_expression) ]
		| insert(ID::div) [ unary_expression >> +(match(lexid::fwdslash, false) >> multiplicative_expression) ]
		| unary_expression
		;
	
	unary_expression =
		  match_insert(lexid::int_literal, ID::int_literal)
		| match_insert(lexid::bool_literal, ID::bool_literal)
		| match_insert(lexid::real_literal, ID::real_literal)
		| new_
		| function_call
		| identifier
		| address_of
		| ( match(lexid::lparen, false) >> expression >> match(lexid::rparen, false) )
		| dereference
		;

	// reused for both parameters and regular declarations
	variable_definition =
		insert(ID::variable_definition) [
			identifier >> match(lexid::as_keyword, false) >> type >> !(match(lexid::lparen, false) >> expression >> match(lexid::rparen, false))
		]
		;
	
	new_ =
		match_insert(lexid::new_keyword, ID::new_) [
			type >> !(match(lexid::lparen, false) >> expression >> match(lexid::rparen, false))
		]
		;
	
	
	
	
	statement =
		  variable_definition_statement
		| expression_statement
		| return_statement
		//| 
		| if_statement
		| delete_statement
		;
	
	expression_statement =
		expression >> ending_semi;
	
	return_statement =
		match_insert(lexid::return_keyword, ID::return_statement) [
			 !expression
		]
		>> ending_semi
		;
	
	variable_definition_statement =
		variable_definition >> ending_semi;
	
	if_statement =
		match_insert(lexid::if_keyword, ID::if_statement) [
			expression >> match(lexid::colon, false),
				block,
			
			(match(lexid::else_keyword, false) >> match(lexid::colon, false),
				block)
			
			// the else expression is optional, but we want to make sure we insert a redundant block anyway
			| ( insert(ID::block, value_t()) )
			
		]
		;
	
	delete_statement =
		match_insert(lexid::delete_keyword, ID::delete_) [
			expression
		]
		>> ending_semi
		;
	
	function = 
		match_insert(lexid::function_keyword, parsid::function) [
			identifier,
			//match(lexid::as_keyword, false),
			function_parameter_list,
			(arrow >> type) | insert(ID::void_type, sooty::value_t()),
			match(lexid::colon, false),
			block
		];
	
	start =
		*(function) //.on_success( print_next_lexid("after function: ") ))
		;

}

void prism::parse( lexeme_list::const_iterator begin, lexeme_list::const_iterator end, parseme_ptr& dest )
{
	initialise_parsers();
	start(begin, end, dest);
}